Skip to content

feat(cli): add implementation plan and project scaffolding for Terminal49 CLI#177

Open
dodeja wants to merge 5 commits into
mainfrom
claude/typescript-sdk-cli-D8s0i
Open

feat(cli): add implementation plan and project scaffolding for Terminal49 CLI#177
dodeja wants to merge 5 commits into
mainfrom
claude/typescript-sdk-cli-D8s0i

Conversation

@dodeja
Copy link
Copy Markdown
Contributor

@dodeja dodeja commented Feb 7, 2026

Comprehensive plan for building a CLI on top of @terminal49/sdk targeting
LLM agents, chat interfaces, and human developers. Includes:

  • PLAN.md: Full architecture, command surface, output modes, LLM agent
    design patterns, error handling, and 4-phase implementation roadmap
  • Test plan: ~141 tests across unit/integration/e2e with 90%+ coverage
    targets enforced via vitest thresholds
  • Project scaffold: package.json, tsconfig, biome, vitest config, and
    directory structure with annotated placeholder modules

https://claude.ai/code/session_01361NQzDnx8FQSPizmZmjrW

Greptile Summary

This PR scaffolds a comprehensive CLI tool (@terminal49/cli) built on top of the existing TypeScript SDK, designed for dual consumption by LLM agents and human developers.

What Changed:

  • Added complete implementation plan (PLAN.md) with architecture, 141-test roadmap, and LLM-specific design patterns
  • Project scaffold with TypeScript, Vitest (90%+ coverage thresholds), Biome linting, and Commander.js
  • Core infrastructure: config management with XDG compliance and secure file permissions (0o600), client factory with token resolution, POSIX-compliant error mapping
  • Command implementations for containers, shipments, tracking requests, search, config, and metadata endpoints
  • Output formatters supporting JSON envelopes (LLM-optimized), table rendering (TTY), and field projection (--fields)
  • Live fixture capture script and smoke tests validating output against real API responses

Notable Design Decisions:

  • Predictable JSON envelope format ({ok, command, data, pagination, meta}) for LLM consumption
  • TTY auto-detection: JSON for pipes, tables for interactive terminals
  • Token-efficient --compact and --fields flags for agent workflows
  • Security: config files stored with 0o600 permissions, atomic writes via temp files

Issues Identified in Previous Threads:

  • bin/t49.ts:12 uses program.parse() instead of parseAsync() — async command handlers won't be awaited
  • src/index.ts:36 hardcodes version 0.1.0 instead of importing from package.json
  • src/index.ts:40,46 has duplicate flags: --raw and --format raw represent the same output mode
  • src/index.ts:38,39 has conflicting --json and --table flags without mutual exclusion enforcement

Confidence Score: 4/5

  • This PR is safe to merge with minor issues that should be addressed in follow-up commits
  • Score of 4 reflects that this is primarily a well-structured scaffold with comprehensive planning, but has 4 known issues flagged in previous threads (async parse, hardcoded version, duplicate/conflicting flags). The core implementation is solid: secure config handling, proper error mapping, and good separation of concerns. However, the identified issues affect CLI behavior and should be fixed before the CLI reaches general availability.
  • Pay attention to bin/t49.ts and src/index.ts which contain the issues identified in previous review threads

Important Files Changed

Filename Overview
sdks/typescript-sdk-cli/PLAN.md Comprehensive implementation plan with architecture, command surface, test plan (~141 tests), and LLM agent design patterns
sdks/typescript-sdk-cli/bin/t49.ts CLI entry point using program.parse() instead of parseAsync() (async command handlers won't be awaited)
sdks/typescript-sdk-cli/src/index.ts Program setup with hardcoded version and conflicting output flags (--raw duplicates --format raw, --json/--table not mutually exclusive)
sdks/typescript-sdk-cli/src/client-factory.ts Token resolution with proper priority order (flag > env > config), includes auth scheme detection
sdks/typescript-sdk-cli/src/config.ts Secure config file handling with atomic writes, proper permissions (0o600), XDG compliance, and input sanitization
sdks/typescript-sdk-cli/src/errors.ts SDK error mapping to CLI exit codes with proper POSIX compliance and structured error envelopes
sdks/typescript-sdk-cli/src/output/table.ts Complex table renderer with deep field searching, search result enrichment, and auto-column inference (517 lines)
sdks/typescript-sdk-cli/vitest.config.ts Test configuration with 90% line/function/statement and 85% branch coverage thresholds

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Start([User runs t49 command]) --> Parse[bin/t49.ts: createProgram & parse args]
    Parse --> Hook[preAction hook: set auth scheme env var]
    Hook --> Factory[client-factory: resolve token<br/>flag > env > config file]
    Factory --> Config{Token found?}
    Config -->|No| AuthError[Throw AuthenticationError<br/>exit code 3]
    Config -->|Yes| CreateClient[Create Terminal49Client<br/>with token, baseUrl, format]
    CreateClient --> Command[Execute command action<br/>e.g., containers.get]
    Command --> SDK[Call @terminal49/sdk method]
    SDK --> Error{Error?}
    Error -->|Yes| MapError[errors.ts: map SDK error<br/>to CLI exit code]
    MapError --> FormatError[formatter: output JSON/table error]
    FormatError --> Exit1[process.exit with mapped code]
    Error -->|No| FormatSuccess[formatter: detect TTY<br/>apply --fields projection]
    FormatSuccess --> Output{Output mode?}
    Output -->|--json or pipe| JSON[json.ts: success envelope]
    Output -->|--table or TTY| Table[table.ts: render table]
    JSON --> Stdout[Write to stdout]
    Table --> Stdout
    Stdout --> Exit0[exit 0]
Loading

Last reviewed commit: 45a9f42

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Error Error Mar 18, 2026 5:54am

Request Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa09382aff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +9 to +10
"main": "dist/index.js",
"types": "dist/index.d.ts",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point main/types to compiled path

With tsconfig.json using rootDir: "." and include: ["src/**/*", "bin/**/*"], the compiled entry for src/index.ts will land at dist/src/index.js (and dist/src/index.d.ts). The current main/types fields point to dist/index.*, which won’t exist after tsc runs. Any consumer doing import/require of the package entry will fail at runtime or during type resolution. Adjust these fields to the emitted path (or change rootDir/outDir layout) to avoid broken package entrypoints.

Useful? React with 👍 / 👎.

…al49 CLI

Comprehensive plan for building a CLI on top of @terminal49/sdk targeting
LLM agents, chat interfaces, and human developers. Includes:

- PLAN.md: Full architecture, command surface, output modes, LLM agent
  design patterns, error handling, and 4-phase implementation roadmap
- Test plan: ~141 tests across unit/integration/e2e with 90%+ coverage
  targets enforced via vitest thresholds
- Project scaffold: package.json, tsconfig, biome, vitest config, and
  directory structure with annotated placeholder modules

https://claude.ai/code/session_01361NQzDnx8FQSPizmZmjrW
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

Comment thread sdks/typescript-sdk-cli/bin/t49.ts Outdated
Comment on lines +12 to +13
const program = createProgram();
program.parse(process.argv);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

program.parse will not await async command handlers

All CLI commands will make HTTP requests via the SDK (async operations). Commander's program.parse() does not await async .action() handlers — they run as fire-and-forget Promises, meaning errors are silently swallowed and process.exit may be called before the async work completes.

This should use parseAsync at the entry point:

Suggested change
const program = createProgram();
program.parse(process.argv);
import { createProgram } from '../src/index.js';
const program = createProgram();
await program.parseAsync(process.argv);

Because of the top-level await, bin/t49.ts must also be compiled with a target/module that supports it (already satisfied by "target": "ES2022" + "module": "NodeNext").

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk-cli/bin/t49.ts
Line: 12-13

Comment:
**`program.parse` will not await async command handlers**

All CLI commands will make HTTP requests via the SDK (async operations). Commander's `program.parse()` does **not** await async `.action()` handlers — they run as fire-and-forget Promises, meaning errors are silently swallowed and `process.exit` may be called before the async work completes.

This should use `parseAsync` at the entry point:

```suggestion
import { createProgram } from '../src/index.js';

const program = createProgram();
await program.parseAsync(process.argv);
```

Because of the top-level `await`, `bin/t49.ts` must also be compiled with a target/module that supports it (already satisfied by `"target": "ES2022"` + `"module": "NodeNext"`).

How can I resolve this? If you propose a fix, please make it concise.

Comment thread sdks/typescript-sdk-cli/src/index.ts Outdated
// Global flags
.option('--json', 'Force JSON output')
.option('--table', 'Force table output')
.option('--raw', 'Output raw JSON:API document')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded version will drift from package.json

.version('0.1.0') duplicates the version from package.json. As the package is bumped these will diverge silently. Since resolveJsonModule: true is already set in tsconfig.json, the version can be imported directly:

import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { version } = require('../package.json');

Then replace the hardcoded string:

Suggested change
.option('--raw', 'Output raw JSON:API document')
.version(version)
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk-cli/src/index.ts
Line: 22

Comment:
**Hardcoded version will drift from `package.json`**

`.version('0.1.0')` duplicates the version from `package.json`. As the package is bumped these will diverge silently. Since `resolveJsonModule: true` is already set in `tsconfig.json`, the version can be imported directly:

```
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { version } = require('../package.json');
```

Then replace the hardcoded string:
```suggestion
    .version(version)
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +24 to +29
.option('--fields <fields>', 'Comma-separated field projection')
.option('--token <token>', 'API token (overrides env/config)')
.option('--base-url <url>', 'API base URL override')
.option('--format <format>', 'Response format: raw | mapped | both', 'mapped')
.option('-q, --quiet', 'Suppress non-data output')
.option('-v, --verbose', 'Verbose diagnostics to stderr')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--raw flag duplicates --format raw

Line 24 adds a standalone --raw flag ("Output raw JSON:API document"), while line 29 adds --format <format> which already accepts raw as a valid value. These two flags represent the same output mode. Having both creates ambiguous precedence (what wins when --raw and --format mapped are both passed?) and will confuse both human users and LLM agents discovering the CLI via t49 commands --json.

Consider removing --raw and making --format raw the canonical way to request the raw JSON:API document.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk-cli/src/index.ts
Line: 24-29

Comment:
**`--raw` flag duplicates `--format raw`**

Line 24 adds a standalone `--raw` flag ("Output raw JSON:API document"), while line 29 adds `--format <format>` which already accepts `raw` as a valid value. These two flags represent the same output mode. Having both creates ambiguous precedence (what wins when `--raw` and `--format mapped` are both passed?) and will confuse both human users and LLM agents discovering the CLI via `t49 commands --json`.

Consider removing `--raw` and making `--format raw` the canonical way to request the raw JSON:API document.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread sdks/typescript-sdk-cli/src/index.ts Outdated
Comment on lines +21 to +23
.option('--table', 'Force table output')
.option('--raw', 'Output raw JSON:API document')
.option('--compact', 'Minified JSON (reduces LLM token usage)')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--json and --table are mutually exclusive but not enforced

Both --json and --table can be passed simultaneously. Commander does not auto-detect conflicting flags, so the formatter will receive both as true with no clear winner. Even as a scaffold, adding a note or using a Commander .addOption with .conflicts() will prevent future confusion:

.addOption(new Option('--json', 'Force JSON output').conflicts('table'))
.addOption(new Option('--table', 'Force table output').conflicts('json'))
Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk-cli/src/index.ts
Line: 21-23

Comment:
**`--json` and `--table` are mutually exclusive but not enforced**

Both `--json` and `--table` can be passed simultaneously. Commander does not auto-detect conflicting flags, so the formatter will receive both as `true` with no clear winner. Even as a scaffold, adding a note or using a Commander `.addOption` with `.conflicts()` will prevent future confusion:

```ts
.addOption(new Option('--json', 'Force JSON output').conflicts('table'))
.addOption(new Option('--table', 'Force table output').conflicts('json'))
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +46 to +47
"devDependencies": {
"typescript": "^5.6.3",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vitest version pinned to ^4.0.13 — verify this is intentional

Vitest ^4.x is a very recent major release. Confirm this version is available and stable in your environment; the ^ range will pick up any 4.x patch/minor automatically, which may be fine, but it is worth validating the major version bump from the more widely-used ^2.x series is deliberate and that the API used in vitest.config.ts (configDefaults, defineConfig) is still compatible.

Prompt To Fix With AI
This is a comment left during a code review.
Path: sdks/typescript-sdk-cli/package.json
Line: 46-47

Comment:
**`vitest` version pinned to `^4.0.13` — verify this is intentional**

Vitest `^4.x` is a very recent major release. Confirm this version is available and stable in your environment; the `^` range will pick up any `4.x` patch/minor automatically, which may be fine, but it is worth validating the major version bump from the more widely-used `^2.x` series is deliberate and that the API used in `vitest.config.ts` (`configDefaults`, `defineConfig`) is still compatible.

How can I resolve this? If you propose a fix, please make it concise.

…e --raw, enforce --json/--table exclusion

- Use parseAsync() instead of parse() so async command handlers are properly awaited
- Import version from package.json instead of hardcoding '0.1.0'
- Remove --raw flag (duplicated --format raw)
- Add mutual exclusion check for --json and --table in preAction hook
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 18, 2026

Target branch is in the excluded branches list.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

.filter((item: any) => item.attributes?.event?.startsWith('rail.'))

P2 Badge Recognize the actual rail transport event names

The transport-event values in docs/openapi.json use names like container.transport.rail_unloaded, but this filter only matches an rail. prefix. As written, getRailMilestones() drops every rail event, so t49 containers rail will always report rail_events: [] even when the container has rail milestones in the API response.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +30 to +31
const require = createRequire(import.meta.url);
const { version } = require('../package.json') as { version: string };
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Load the CLI version from the package root

After tsc, this module is emitted as dist/src/index.js, so require('../package.json') resolves to dist/package.json. We do not emit that file anywhere, which means every built invocation of the CLI (node dist/bin/t49.js, a packed tarball, or a published package) will fail with MODULE_NOT_FOUND before any command runs.

Useful? React with 👍 / 👎.

Comment on lines +1156 to +1159
data: {
type: 'custom_field',
id: fieldId,
value,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Build custom-field payloads with attributes.api_slug

docs/openapi.json defines both POST /shipments/{shipment_id}/custom_fields and POST /containers/{container_id}/custom_fields with a body shaped like data.attributes.{api_slug,value}. This code sends data.id and data.value instead, so shipments set-custom-field and the matching container command will submit a schema-invalid payload and get a 4xx response on every write. The same malformed payload is duplicated in setContainerCustomField.

Useful? React with 👍 / 👎.

- Add missing .js extension to config import in client-factory.ts
- Fix CliConfig → Record<string, unknown> cast in writeConfig
- Fix reduce() generic type in fields.ts getValue()
- Fix workspace:* → * for npm workspace compatibility
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants